﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.Data.SqlClient;
using System.Data;
using System.Data.Common;

namespace Ruandao.MiniAccess
{

    /// <summary>
    /// EntityAccess具体类型的基类实现，主要功能是对Database类的进一步封装，提供了针对Enitity类的工具方法。
    /// 主要实现思想是通过内部的Database对象和表示实体关系映射表TableMaps来自动提供Entity的增删改查的功能。
    /// </summary>
    abstract public class AccessBase
    {
        private Database database;

        /// <summary>
        /// 
        /// </summary>
        protected static IDictionary<string, TableMap> tableMaps;

        /// <summary>
        /// 构造。内部的TableMaps在具体的实现类中用静态构造的方式来提供数据。
        /// </summary>
        /// <param name="database">指明所要处理数据库。</param>
        public AccessBase( Database database )
        {
            this.database = database;
        }

        /// <summary>
        /// 从内部TableMaps映射字典中取出指定实体的ER映射数据，内部的TableMaps在具体的实现类中用静态构造的方式来提供数据。
        /// <br/>异常：OrMapException(指定实体的OR映射数据没有定义)。
        /// </summary>
        /// <param name="entityType">实体的类型。</param>
        /// <returns>实体和表的映射对象。</returns>
        public TableMap GetTableMap(Type entityType)
        {
            if (AccessBase.tableMaps.Keys.Contains(entityType.FullName))
                return AccessBase.tableMaps[entityType.FullName];
            else
                throw new OrMapException("无法获取类型为" + entityType.FullName + "的实体的映射数据。");
        }

        /// <summary>
        /// 获取内部操作的数据库对象。
        /// </summary>
        /// <returns>数据库对象</returns>
        public Database GetDatabase()
        {
            return this.database;
        }

        /// <summary>
        /// 根据主键删除记录，可能返回0。
        /// <br/>异常：OrMapException(没有提供实体的删除命令)，SqlException(映射中指定的SQL命令不合法)
        /// </summary>
        /// <param name="keys">主键值。</param>
        /// <returns>实际删除的记录数。</returns>
        virtual public int DeleteEntity<T>(params object[] keys)
        {
            TableMap tableMap = GetTableMap(typeof(T));
            if (string.IsNullOrEmpty(tableMap.DeleteCommand))
            {
                throw new OrMapException("没有提供实体的删除命令。");
            }

            return GetDatabase().ExecuteNonQuery(tableMap.DeleteCommand, keys);
        }

        /// <summary>
        /// 按条件删除记录，可能返回0。
        /// <br/>异常：SqlException(命令执行出错，条件字符串格式错误)
        /// </summary>
        /// <param name="conditions">包含WHERE条件字符串。</param>
        /// <param name="parameterValues">WHERE条件字符串中所需要的参数值，按参数出现的先后顺序匹配。</param>
        /// <returns>实际删除的记录数。</returns>
        virtual public int DeleteEntities<T>(string conditions, params object[] parameterValues)
        {
            TableMap tableMap = GetTableMap(typeof(T));
            return GetDatabase().ExecuteNonQuery(tableMap.DeleteByConditionsCommand + conditions, parameterValues );
        }

        /// <summary>
        /// 插入记录。
        /// 约定参数名称就是"@列名"，对于不支持名称参数的数据库，需要在子类中覆盖InsertEntity&lt;T>()。
        /// <br/>异常：SqlException(插入数据违反数据约束，映射中指定的SQL命令不合法。)
        /// </summary>
        /// <param name="t">要插入到数据库中的实体。</param>
        /// <returns>实际插入的记录数。</returns>
        public virtual int InsertEntity<T>(T t) where T : EntityBase<T>, new()
        {
            Type entityType = typeof(T);
            TableMap tableMap = GetTableMap(entityType);

            using (DbCommand cmd = this.database.GetCommand(tableMap.InsertCommand))
            {
                foreach( DbParameter p in cmd.Parameters)
                {
                    if (p.Direction == ParameterDirection.Output || p.Direction == ParameterDirection.ReturnValue)
                        continue;

                    ColumnMap cm = tableMap.GetColumn(p.ParameterName.Substring(1)); // 取出@后面的字符作为列的名称，进而得到列对象。
                    try
                    {
                        object value = entityType.GetProperty(cm.PropertyName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).GetValue(t, null);
                        p.Value = value?? DBNull.Value;
                    }
                    catch (AmbiguousMatchException e)
                    {
                        throw new OrMapException("实体" + entityType.FullName + "中没有找到指定的属性：" + cm.PropertyName, e);
                    }
                }

                int result = GetDatabase().ExecuteNonQuery(cmd);

                foreach (ColumnMap cm in tableMap.AllColumnMaps)
                {
                    if (!cm.IsInsert)
                    {
                        try
                        {
                            entityType.GetProperty(cm.PropertyName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).SetValue(t, GetDatabase().GetParameterValue(cmd, cm.ColumnName), null);
                        }
                        catch (AmbiguousMatchException e)
                        {
                            throw new OrMapException("实体" + entityType.FullName + "中没有找到指定的属性：" + cm.PropertyName, e);
                        }
                    }
                }
                t.IsNew = false;
                return result;
            } // end of using...
        }

        /// <summary>
        /// 修改记录，可能返回0。
        /// <br/>异常：OrMapException(没有提供有效的修改命令)，SqlException(命令执行出错，条件字符串格式错误)
        /// </summary>
        /// <param name="t">要修改的实体。</param>
        /// <param name="keys">要修改实体的主键值，若为复合主键按顺序提供。</param>
        /// <returns>实际修改的记录数。</returns>
        public int UpdateEntity<T>(T t, params object[] keys) where T : EntityBase<T>, new()
        {
            Type entityType = typeof(T);
            TableMap tableMap = GetTableMap(entityType);
            if (string.IsNullOrEmpty(tableMap.UpdateCommand))
            {
                throw new Exception("在OR映射中没有指定有效的记录更新命令。");
            }

            using (DbCommand cmd = GetDatabase().GetCommand(tableMap.UpdateCommand))
            {
                char prefix = GetDatabase().ParameterToken;
                foreach (ColumnMap cm in tableMap.AllColumnMaps)
                {
                    if (cm.IsUpdate)
                    {
                        object propValue;
                        try
                        {
                            propValue = entityType.GetProperty(cm.PropertyName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).GetValue(t, null);
                        }
                        catch (AmbiguousMatchException e)
                        {
                            throw new OrMapException("实体" + entityType.FullName + "中没有找到指定的属性：" + cm.PropertyName, e);
                        }

                        string paraName = cm.ColumnName;
                        if (tableMap.IsPrimaryKey(paraName)) paraName = "New_" + paraName;

                        if (propValue == null)
                            cmd.Parameters[prefix + paraName].Value = DBNull.Value;
                        else
                            cmd.Parameters[prefix + paraName].Value = propValue;

                    }
                }

                // params object[] keys 的顺序是表中的字段定义顺序。
                int i = 0; 
                foreach (ColumnMap cm in tableMap.PrimaryKeyColumnMaps)
                {
                    cmd.Parameters[prefix + cm.ColumnName].Value = keys[i];
                    i++;
                }

                return GetDatabase().ExecuteNonQuery(cmd);
            } // end of using...
        }

        /// <summary>
        /// 按条件查找实体列表。
        /// <br/>异常：SqlException(命令执行出错，条件字符串格式错误)
        /// </summary>
        /// <param name="condition">带WHERE的条件字符串。</param>
        /// <param name="parameterValues">按顺序提供的参数值。</param>
        /// <returns>实体列表。</returns>
        public IList<T> SearchEntities<T>(string condition, params object[] parameterValues) where T : EntityBase<T>, new()
        {
            Type entityType = typeof(T);
            TableMap tableMap = GetTableMap(entityType);

            IList<T> entities = new List<T>();

            using (IDataReader reader = GetDatabase().ExecuteReader(tableMap.SearchCommand + condition, parameterValues))
            {

                while (reader.Read())
                {
                    T t = new T();
                    LoadFromReader(t, reader);
                    entities.Add(t);
                }
            } // end of using ...
            return entities;
        }

        /// <summary>
        /// 按条件查找实体，允许返回null。
        /// <br/>异常：SqlException(命令执行出错，条件字符串格式错误)
        /// </summary>
        /// <param name="condition">带WHERE的条件字符串。</param>
        /// <param name="parameterValues">按顺序提供的参数值。</param>
        /// <returns>查到的第一个实体。</returns>
        public T FindEntity<T>(string condition, params object[] parameterValues) where T : EntityBase<T>, new()
        {
            TableMap tableMap = GetTableMap(typeof(T));
            using (IDataReader reader = GetDatabase().ExecuteReader(tableMap.SearchCommand + condition, parameterValues))
            {
                while (reader.Read())
                {
                    T t = new T();
                    LoadFromReader(t, reader);
                    return t;
                }
            } // end of using ...
            return default(T);
        }

        /// <summary>
        /// 根据主键查找实体，允许返回null。
        /// <br/>异常：SqlException(命令执行出错，条件字符串格式错误)
        /// </summary>
        /// <param name="keys">主键，若为复合主键，按顺序提供参数值。</param>
        /// <returns>实体。</returns>
        public T FetechEntity<T>(params object[] keys) where T : EntityBase<T>, new()
        {
            TableMap tableMap = GetTableMap(typeof(T));

            if (string.IsNullOrEmpty(tableMap.FetchCommand))
            {
                throw new OrMapException("在OR映射中没有指定有效的FetchCommand");
            }

            using (IDataReader reader = GetDatabase().ExecuteReader(tableMap.FetchCommand, keys))
            {

                T t = new T();
                if (reader.Read())
                {
                    LoadFromReader(t, reader);
                    return t;
                }
                return default(T);
            } // end of using ...
        }

        /// <summary>
        /// 读取数据库中一条记录的内容到实体中。如果使用了该方法，则需要在结束后释放资源，最好是在using语法中使用。
        /// </summary>
        /// <param name="t">存放读取出来的数据的实体。</param>
        /// <param name="reader">指向一条记录。</param>
        public void LoadFromReader<T>(T t, IDataReader reader) where T : EntityBase<T>, new()
        {
            int i;
            Type entityType = typeof(T);
            TableMap tableMap = GetTableMap(entityType);
            PropertyInfo propInfo;
            if (reader != null && !reader.IsClosed)
            {
                foreach (ColumnMap cm in tableMap.AllColumnMaps)
                {
                    try
                    {
                        propInfo = entityType.GetProperty(cm.PropertyName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
                    }
                    catch (AmbiguousMatchException e)
                    {
                        throw new OrMapException("实体" + entityType.FullName + "中没有找到指定的属性：" + cm.PropertyName, e);
                    }

                    i = reader.GetOrdinal(cm.ColumnName);
                    if (!reader.IsDBNull(i))
                    {
                        object columnValue = reader.GetValue(i);

                        Type propType = propInfo.PropertyType;

                        if (propType.FullName.StartsWith("System.Nullable"))
                        {
                            if (propType.GetGenericArguments()[0].IsEnum && columnValue is string)
                            {
                                propInfo.SetValue(t, Enum.Parse(propType.GetGenericArguments()[0], columnValue.ToString()), null);
                            }
                            else
                            {
                                propInfo.SetValue(t, columnValue, null);
                            }
                        }
                        else
                        {
                            if (propType.IsEnum && columnValue is string)
                            {
                                propInfo.SetValue(t, Enum.Parse(propType, columnValue.ToString()), null);
                            }
                            else
                            {
                                propInfo.SetValue(t, columnValue, null);
                            }
                        }
                    }
                    else  // is a dbnull value
                    {
                        propInfo.SetValue(t, null, null);
                    }
                }
            }
            t.IsNew = false;
        }

    }

}
